package matrix;

import org.apache.log4j.Logger;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Random;

/**
 * Created with IntelliJ IDEA.
 * User: aleks_000
 * Date: 29.09.13
 * Time: 16:44
 * To change this template use File | Settings | File Templates.
 */
public abstract class BaseMatrix implements Matrix {
    protected int columnCount;
    protected int rowCount;

    protected static final String MATRIX_START = "[ ";
    protected static final String MATRIX_END = " ]";
    protected static final String ROW_DELIMITER = ";";
    protected static final String CELL_DELIMITER = " ";
    protected static final Random RANDOM = new Random();

    private static final Logger LOGGER = Logger.getLogger(BaseMatrix.class);

    protected BaseMatrix(int rowCount, int columnCount) {
        if (rowCount <= 0) {
            throw new IllegalArgumentException();
        }
        this.rowCount = rowCount;

        if (columnCount <= 0) {
            throw new IllegalArgumentException();
        }
        this.columnCount = columnCount;
    }

    public int getRowCount() {
        return rowCount;
    }

    public int getColumnCount() {
        return columnCount;
    }

    public abstract double getValue(int row, int column);

    public abstract void setValue(int row, int column, double value);

    protected static  <T extends Matrix> T multiply(Matrix first, Matrix second, MatrixMultiplier matrixMultiplier,
                                          MatrixFactory<T> matrixFactory) throws MatrixIncompatibleException {
        if (first == null) {
            throw new NullPointerException();
        }
        if (second == null) {
            throw new NullPointerException();
        }
        if (matrixMultiplier == null) {
            throw new NullPointerException();
        }
        if (matrixFactory == null) {
            throw new NullPointerException();
        }
        return matrixMultiplier.multiply(first, second, matrixFactory);
    }

    public String toString() {
        StringBuilder sb = new StringBuilder(32);
        // start matrix
        sb.append(MATRIX_START);
        // cycle by rows
        for (int row = 0; row < this.getRowCount(); ++row) {
           for (int column = 0; column < this.getColumnCount(); ++column) {
               if (row != 0 && column == 0) {
                   sb.append(CELL_DELIMITER);
               }
               sb.append(getValue(row, column));
                if (column != getColumnCount() - 1) {
                    sb.append(CELL_DELIMITER);
                }
            }
            sb.append(ROW_DELIMITER);
        }
        sb.append(MATRIX_END);

        return sb.toString();
    }

    private void writeObject(ObjectOutputStream out) throws IOException {
        out.writeInt(rowCount);
        out.writeInt(columnCount);
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        rowCount = in.readInt();
        columnCount = in.readInt();
    }

    protected static  <T extends Matrix> T parse(String str, MatrixFactory<T> matrixFactoryMethod) {
        // get clear matrix
        // todo: create a MatrixFormatException
        if (str == null) {
            throw new NullPointerException();
        }

        String clearMatrix = str.substring(MATRIX_START.length(), str.length() - MATRIX_END.length());
        clearMatrix = clearMatrix.trim();
        clearMatrix.replaceAll(" +", " ");
        String[] rows = clearMatrix.split(ROW_DELIMITER);
        T matrix = null;
        for (int rowIndex = 0; rowIndex < rows.length; ++rowIndex) {
            rows[rowIndex] = rows[rowIndex].trim();
            String[] values = rows[rowIndex].split(CELL_DELIMITER);
            if (matrix == null) {
                matrix = matrixFactoryMethod.create(rows.length, values.length);
            }
            for (int columnIndex = 0; columnIndex < values.length; ++columnIndex) {
                matrix.setValue(rowIndex, columnIndex, Double.parseDouble(values[columnIndex]));
            }
        }
        return matrix;
    }

    protected static <T extends Matrix> T createIdentity(int size, MatrixFactory<T> matrixFactoryMethod) {
        T matrix = matrixFactoryMethod.create(size, size);
        for (int row = 0; row < size; ++row) {
            for (int column = 0; column < size; ++column) {
                if (column == row) {
                    matrix.setValue(row, column, 1.0);
                }
                else {
                    matrix.setValue(row, column, 0.0);
                }
            }
        }
        return matrix;
    }

    protected static <T extends Matrix> T create(int rowCount, int columnCount, MatrixFactory<T> matrixFactoryMethod) {
        T matrix = matrixFactoryMethod.create(rowCount, columnCount);
        for (int row = 0; row < rowCount; ++row) {
            for (int column = 0; column < columnCount; ++column) {
                matrix.setValue(row, column, 0.0);
            }
        }
        return matrix;
    }

    protected static <T extends Matrix> T createRandom(int rowCount, int columnCount, MatrixFactory<T> matrixFactoryMethod) {
        T matrix = matrixFactoryMethod.create(rowCount, columnCount);
        for (int row = 0; row < rowCount; ++row) {
            for (int column = 0; column < columnCount; ++column) {
                matrix.setValue(row, column, RANDOM.nextDouble());
            }
        }
        return matrix;
    }
}
